package com.ifacebox.web.common.manager;

import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author znn
 * @date 2021/1/25 22:26
 */
@Component
public class RedisManager {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public ValueOperations<String, Object> opsForValue() {
        return redisTemplate.opsForValue();
    }

    public ListOperations<String, Object> opsForList() {
        return redisTemplate.opsForList();
    }

    public SetOperations<String, Object> opsForSet() {
        return redisTemplate.opsForSet();
    }

    /**
     * DEL key [key ...]
     * 删除给定的一个或多个 key 。
     * 不存在的 key 会被忽略。
     *
     * @param key 键
     * @return 被删除 key 的数量。
     */
    public boolean del(String... key) {
        if (ObjectUtils.isEmpty(key)) {
            return false;
        }
        if (key.length == 1) {
            Boolean result = redisTemplate.delete(key[0]);
            return ObjectUtils.isEmpty(result) ? false : result.booleanValue();
        }
        Long result = redisTemplate.delete(CollectionUtils.arrayToList(key));
        return ObjectUtils.isEmpty(result) ? false : key.length == result.intValue();
    }

    /**
     * EXISTS key
     * 检查给定 key 是否存在。
     *
     * @param key 键
     * @return 若 key 存在，返回 1 ，否则返回 0 。
     */
    public boolean exists(String key) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }
        Boolean result = redisTemplate.hasKey(key);
        return ObjectUtils.isEmpty(result) ? false : result.booleanValue();
    }

    /**
     * EXPIRE key seconds
     * 为给定 key 设置生存时间，当 key 过期时(生存时间为 0 )，它会被自动删除。
     *
     * @param key     键
     * @param timeout 时间(秒)
     * @return 设置成功返回 1 ，否则返回 0 。
     */
    public boolean expire(String key, long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * PEXPIRE key milliseconds
     * 这个命令和 EXPIRE 命令的作用类似，但是它以毫秒为单位设置 key 的生存时间，而不像 EXPIRE 命令那样，以秒为单位。
     *
     * @param key     键
     * @param timeout 时间
     * @param unit
     * @return 设置成功返回 1 ，否则返回 0 。
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        if (StringUtils.isEmpty(key) || timeout < 0) {
            return false;
        }
        Boolean result = redisTemplate.expire(key, timeout, unit);
        return ObjectUtils.isEmpty(result) ? false : result.booleanValue();
    }

    /**
     * EXPIREAT key timestamp
     * EXPIREAT 的作用和 EXPIRE 类似，都用于为 key 设置生存时间。
     * 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
     *
     * @param key  键
     * @param date 时间
     * @return 设置成功返回 1 ，否则返回 0 。
     */
    public boolean expire(String key, Date date) {
        if (StringUtils.isEmpty(key) || ObjectUtils.isEmpty(date)) {
            return false;
        }
        Boolean result = redisTemplate.expireAt(key, date);
        return ObjectUtils.isEmpty(result) ? false : result.booleanValue();
    }

    /**
     * TTL key
     * 以秒为单位，返回给定 key 的剩余生存时间(TTL, time to live)。
     *
     * @param key 键
     * @return 当 key 不存在时，返回 -2 。
     * 当 key 存在但没有设置剩余生存时间时，返回 -1 。
     * 否则，以秒为单位，返回 key 的剩余生存时间。
     */
    public long ttl(String key) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        Long result = redisTemplate.getExpire(key);
        return ObjectUtils.isEmpty(result) ? 0 : result.longValue();
    }

    /**
     * PTTL key
     * 这个命令类似于 TTL 命令，但它以毫秒为单位返回 key 的剩余生存时间，而不是像 TTL 命令那样，以秒为单位。
     *
     * @param key  键
     * @param unit
     * @return 当 key 不存在时，返回 -2 。
     * 当 key 存在但没有设置剩余生存时间时，返回 -1 。
     * 否则，以毫秒为单位，返回 key 的剩余生存时间。
     */
    public long ttl(String key, TimeUnit unit) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        Long result = redisTemplate.getExpire(key, unit);
        return ObjectUtils.isEmpty(result) ? 0 : result.longValue();
    }


    /**
     * INCR key
     * 将 key 中储存的数字值增一。
     * 如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 INCR 操作。
     *
     * @param key 键
     * @return 执行 INCR 命令之后 key 的值。
     */
    public long incr(String key) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        Long result = redisTemplate.opsForValue().increment(key);
        return ObjectUtils.isEmpty(result) ? 0 : result.longValue();
    }

    public long incr(String key, long value) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        Long result = redisTemplate.opsForValue().increment(key, value);
        return ObjectUtils.isEmpty(result) ? 0 : result.longValue();
    }

    public double incr(String key, double value) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        Double result = redisTemplate.opsForValue().increment(key, value);
        return ObjectUtils.isEmpty(result) ? 0 : result.longValue();
    }

    /**
     * DECR key
     * 将 key 中储存的数字值减一。
     * 如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 DECR 操作。
     *
     * @param key 键
     * @return 执行 DECR 命令之后 key 的值。
     */
    public long decr(String key) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        return redisTemplate.opsForValue().decrement(key);
    }

    public long decr(String key, long value) {
        if (StringUtils.isEmpty(key)) {
            return 0;
        }
        Long result = redisTemplate.opsForValue().decrement(key, value);
        return ObjectUtils.isEmpty(result) ? 0 : result.longValue();
    }

    /**
     * SET key value [EX seconds] [PX milliseconds] [NX|XX]
     * 将字符串值 value 关联到 key 。
     * 如果 key 已经持有其他值， SET 就覆写旧值，无视类型。
     *
     * @param key   键
     * @param value 值
     */
    public void set(String key, Object value) {
        if (!StringUtils.isEmpty(key)) {
            redisTemplate.opsForValue().set(key, value);
        }
    }

    public void set(String key, Object value, long timeout, TimeUnit unit) {
        if (!StringUtils.isEmpty(key) && timeout >= 0) {
            redisTemplate.opsForValue().set(key, value, timeout, unit);
        }
    }

    /**
     * GET key
     * 返回 key 所关联的字符串值。
     * 如果 key 不存在那么返回特殊值 nil 。
     * 假如 key 储存的值不是字符串类型，返回一个错误，因为 GET 只能用于处理字符串值。
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return StringUtils.isEmpty(key) ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * GETSET key value
     * 将给定 key 的值设为 value ，并返回 key 的旧值(old value)。
     * 当 key 存在但不是字符串类型时，返回一个错误。
     *
     * @param key   键
     * @param value 值
     * @return 返回给定 key 的旧值。
     * 当 key 没有旧值时，也即是， key 不存在时，返回 nil 。
     */
    public Object get(String key, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return get(key);
        }
        return StringUtils.isEmpty(key) ? null : redisTemplate.opsForValue().getAndSet(key, value);
    }

    public boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
        return ObjectUtils.isEmpty(result) ? false : result.booleanValue();
    }

    public void unLock(String key, String value) {
        if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(value) && value.equals(stringRedisTemplate.opsForValue().get(key))) {
            stringRedisTemplate.delete(key);
        }
    }

    public static class WatchLockDog extends Thread {
        private static final int WATCH_LOCK_DONG_MILLIS = 3;
        @Resource
        private StringRedisTemplate stringRedisTemplate;
        private String key;
        private String value;
        private long timeout;
        private TimeUnit unit;

        public WatchLockDog(String key, String value, long timeout, TimeUnit unit) {
            this.key = key;
            this.value = value;
            this.timeout = timeout;
            this.unit = unit;
            super.setName("Thread-" + key + "-" + value);
            super.setDaemon(true);
        }

        @Override
        public void run() {
            while (true) {
                try {
                    if (StringUtils.isEmpty(this.value) || StringUtils.isEmpty(this.key)) {
                        break;
                    }
                    // 获取锁的value
                    String value = this.stringRedisTemplate.opsForValue().get(this.key);
                    // 判断当前父线程是否已经释放锁，如果父线程已释放，则跳出线程
                    if (StringUtils.isEmpty(value) || !this.value.equals(value)) {
                        break;
                    }
                    // 续命
                    stringRedisTemplate.expire(key, timeout, unit);
                    // 没隔1s续命一次
                    Thread.sleep(timeout/WATCH_LOCK_DONG_MILLIS);
                } catch (Exception e) {
                    break;
                }
            }
        }
    }
}
